home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / lib / python2.5 / mailbox.pyc (.txt) < prev    next >
Python Compiled Bytecode  |  2008-10-29  |  75KB  |  2,593 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.5)
  3.  
  4. '''Read/write support for Maildir, mbox, MH, Babyl, and MMDF mailboxes.'''
  5. import sys
  6. import os
  7. import time
  8. import calendar
  9. import socket
  10. import errno
  11. import copy
  12. import email
  13. import email.Message as email
  14. import email.Generator as email
  15. import rfc822
  16. import StringIO
  17.  
  18. try:
  19.     if sys.platform == 'os2emx':
  20.         raise ImportError
  21.     
  22.     import fcntl
  23. except ImportError:
  24.     fcntl = None
  25.  
  26. __all__ = [
  27.     'Mailbox',
  28.     'Maildir',
  29.     'mbox',
  30.     'MH',
  31.     'Babyl',
  32.     'MMDF',
  33.     'Message',
  34.     'MaildirMessage',
  35.     'mboxMessage',
  36.     'MHMessage',
  37.     'BabylMessage',
  38.     'MMDFMessage',
  39.     'UnixMailbox',
  40.     'PortableUnixMailbox',
  41.     'MmdfMailbox',
  42.     'MHMailbox',
  43.     'BabylMailbox']
  44.  
  45. class Mailbox:
  46.     '''A group of messages in a particular place.'''
  47.     
  48.     def __init__(self, path, factory = None, create = True):
  49.         '''Initialize a Mailbox instance.'''
  50.         self._path = os.path.abspath(os.path.expanduser(path))
  51.         self._factory = factory
  52.  
  53.     
  54.     def add(self, message):
  55.         '''Add message and return assigned key.'''
  56.         raise NotImplementedError('Method must be implemented by subclass')
  57.  
  58.     
  59.     def remove(self, key):
  60.         """Remove the keyed message; raise KeyError if it doesn't exist."""
  61.         raise NotImplementedError('Method must be implemented by subclass')
  62.  
  63.     
  64.     def __delitem__(self, key):
  65.         self.remove(key)
  66.  
  67.     
  68.     def discard(self, key):
  69.         '''If the keyed message exists, remove it.'''
  70.         
  71.         try:
  72.             self.remove(key)
  73.         except KeyError:
  74.             pass
  75.  
  76.  
  77.     
  78.     def __setitem__(self, key, message):
  79.         """Replace the keyed message; raise KeyError if it doesn't exist."""
  80.         raise NotImplementedError('Method must be implemented by subclass')
  81.  
  82.     
  83.     def get(self, key, default = None):
  84.         """Return the keyed message, or default if it doesn't exist."""
  85.         
  86.         try:
  87.             return self.__getitem__(key)
  88.         except KeyError:
  89.             return default
  90.  
  91.  
  92.     
  93.     def __getitem__(self, key):
  94.         """Return the keyed message; raise KeyError if it doesn't exist."""
  95.         if not self._factory:
  96.             return self.get_message(key)
  97.         else:
  98.             return self._factory(self.get_file(key))
  99.  
  100.     
  101.     def get_message(self, key):
  102.         '''Return a Message representation or raise a KeyError.'''
  103.         raise NotImplementedError('Method must be implemented by subclass')
  104.  
  105.     
  106.     def get_string(self, key):
  107.         '''Return a string representation or raise a KeyError.'''
  108.         raise NotImplementedError('Method must be implemented by subclass')
  109.  
  110.     
  111.     def get_file(self, key):
  112.         '''Return a file-like representation or raise a KeyError.'''
  113.         raise NotImplementedError('Method must be implemented by subclass')
  114.  
  115.     
  116.     def iterkeys(self):
  117.         '''Return an iterator over keys.'''
  118.         raise NotImplementedError('Method must be implemented by subclass')
  119.  
  120.     
  121.     def keys(self):
  122.         '''Return a list of keys.'''
  123.         return list(self.iterkeys())
  124.  
  125.     
  126.     def itervalues(self):
  127.         '''Return an iterator over all messages.'''
  128.         for key in self.iterkeys():
  129.             
  130.             try:
  131.                 value = self[key]
  132.             except KeyError:
  133.                 continue
  134.  
  135.             yield value
  136.         
  137.  
  138.     
  139.     def __iter__(self):
  140.         return self.itervalues()
  141.  
  142.     
  143.     def values(self):
  144.         '''Return a list of messages. Memory intensive.'''
  145.         return list(self.itervalues())
  146.  
  147.     
  148.     def iteritems(self):
  149.         '''Return an iterator over (key, message) tuples.'''
  150.         for key in self.iterkeys():
  151.             
  152.             try:
  153.                 value = self[key]
  154.             except KeyError:
  155.                 continue
  156.  
  157.             yield (key, value)
  158.         
  159.  
  160.     
  161.     def items(self):
  162.         '''Return a list of (key, message) tuples. Memory intensive.'''
  163.         return list(self.iteritems())
  164.  
  165.     
  166.     def has_key(self, key):
  167.         '''Return True if the keyed message exists, False otherwise.'''
  168.         raise NotImplementedError('Method must be implemented by subclass')
  169.  
  170.     
  171.     def __contains__(self, key):
  172.         return self.has_key(key)
  173.  
  174.     
  175.     def __len__(self):
  176.         '''Return a count of messages in the mailbox.'''
  177.         raise NotImplementedError('Method must be implemented by subclass')
  178.  
  179.     
  180.     def clear(self):
  181.         '''Delete all messages.'''
  182.         for key in self.iterkeys():
  183.             self.discard(key)
  184.         
  185.  
  186.     
  187.     def pop(self, key, default = None):
  188.         '''Delete the keyed message and return it, or default.'''
  189.         
  190.         try:
  191.             result = self[key]
  192.         except KeyError:
  193.             return default
  194.  
  195.         self.discard(key)
  196.         return result
  197.  
  198.     
  199.     def popitem(self):
  200.         '''Delete an arbitrary (key, message) pair and return it.'''
  201.         for key in self.iterkeys():
  202.             return (key, self.pop(key))
  203.         else:
  204.             raise KeyError('No messages in mailbox')
  205.  
  206.     
  207.     def update(self, arg = None):
  208.         '''Change the messages that correspond to certain keys.'''
  209.         if hasattr(arg, 'iteritems'):
  210.             source = arg.iteritems()
  211.         elif hasattr(arg, 'items'):
  212.             source = arg.items()
  213.         else:
  214.             source = arg
  215.         bad_key = False
  216.         for key, message in source:
  217.             
  218.             try:
  219.                 self[key] = message
  220.             continue
  221.             except KeyError:
  222.                 bad_key = True
  223.                 continue
  224.             
  225.  
  226.         
  227.         if bad_key:
  228.             raise KeyError('No message with key(s)')
  229.         
  230.  
  231.     
  232.     def flush(self):
  233.         '''Write any pending changes to the disk.'''
  234.         raise NotImplementedError('Method must be implemented by subclass')
  235.  
  236.     
  237.     def lock(self):
  238.         '''Lock the mailbox.'''
  239.         raise NotImplementedError('Method must be implemented by subclass')
  240.  
  241.     
  242.     def unlock(self):
  243.         '''Unlock the mailbox if it is locked.'''
  244.         raise NotImplementedError('Method must be implemented by subclass')
  245.  
  246.     
  247.     def close(self):
  248.         '''Flush and close the mailbox.'''
  249.         raise NotImplementedError('Method must be implemented by subclass')
  250.  
  251.     
  252.     def _dump_message(self, message, target, mangle_from_ = False):
  253.         '''Dump message contents to target file.'''
  254.         if isinstance(message, email.Message.Message):
  255.             buffer = StringIO.StringIO()
  256.             gen = email.Generator.Generator(buffer, mangle_from_, 0)
  257.             gen.flatten(message)
  258.             buffer.seek(0)
  259.             target.write(buffer.read().replace('\n', os.linesep))
  260.         elif isinstance(message, str):
  261.             if mangle_from_:
  262.                 message = message.replace('\nFrom ', '\n>From ')
  263.             
  264.             message = message.replace('\n', os.linesep)
  265.             target.write(message)
  266.         elif hasattr(message, 'read'):
  267.             while True:
  268.                 line = message.readline()
  269.                 if line == '':
  270.                     break
  271.                 
  272.                 if mangle_from_ and line.startswith('From '):
  273.                     line = '>From ' + line[5:]
  274.                 
  275.                 line = line.replace('\n', os.linesep)
  276.                 target.write(line)
  277.         else:
  278.             raise TypeError('Invalid message type: %s' % type(message))
  279.  
  280.  
  281.  
  282. class Maildir(Mailbox):
  283.     '''A qmail-style Maildir mailbox.'''
  284.     colon = ':'
  285.     
  286.     def __init__(self, dirname, factory = rfc822.Message, create = True):
  287.         '''Initialize a Maildir instance.'''
  288.         Mailbox.__init__(self, dirname, factory, create)
  289.         if not os.path.exists(self._path):
  290.             if create:
  291.                 os.mkdir(self._path, 448)
  292.                 os.mkdir(os.path.join(self._path, 'tmp'), 448)
  293.                 os.mkdir(os.path.join(self._path, 'new'), 448)
  294.                 os.mkdir(os.path.join(self._path, 'cur'), 448)
  295.             else:
  296.                 raise NoSuchMailboxError(self._path)
  297.         
  298.         self._toc = { }
  299.  
  300.     
  301.     def add(self, message):
  302.         '''Add message and return assigned key.'''
  303.         tmp_file = self._create_tmp()
  304.         
  305.         try:
  306.             self._dump_message(message, tmp_file)
  307.         finally:
  308.             _sync_close(tmp_file)
  309.  
  310.         if isinstance(message, MaildirMessage):
  311.             subdir = message.get_subdir()
  312.             suffix = self.colon + message.get_info()
  313.             if suffix == self.colon:
  314.                 suffix = ''
  315.             
  316.         else:
  317.             subdir = 'new'
  318.             suffix = ''
  319.         uniq = os.path.basename(tmp_file.name).split(self.colon)[0]
  320.         dest = os.path.join(self._path, subdir, uniq + suffix)
  321.         
  322.         try:
  323.             if hasattr(os, 'link'):
  324.                 os.link(tmp_file.name, dest)
  325.                 os.remove(tmp_file.name)
  326.             else:
  327.                 os.rename(tmp_file.name, dest)
  328.         except OSError:
  329.             e = None
  330.             os.remove(tmp_file.name)
  331.             if e.errno == errno.EEXIST:
  332.                 raise ExternalClashError('Name clash with existing message: %s' % dest)
  333.             else:
  334.                 raise 
  335.         except:
  336.             e.errno == errno.EEXIST
  337.  
  338.         if isinstance(message, MaildirMessage):
  339.             os.utime(dest, (os.path.getatime(dest), message.get_date()))
  340.         
  341.         return uniq
  342.  
  343.     
  344.     def remove(self, key):
  345.         """Remove the keyed message; raise KeyError if it doesn't exist."""
  346.         os.remove(os.path.join(self._path, self._lookup(key)))
  347.  
  348.     
  349.     def discard(self, key):
  350.         '''If the keyed message exists, remove it.'''
  351.         
  352.         try:
  353.             self.remove(key)
  354.         except KeyError:
  355.             pass
  356.         except OSError:
  357.             e = None
  358.             if e.errno != errno.ENOENT:
  359.                 raise 
  360.             
  361.         except:
  362.             e.errno != errno.ENOENT
  363.  
  364.  
  365.     
  366.     def __setitem__(self, key, message):
  367.         """Replace the keyed message; raise KeyError if it doesn't exist."""
  368.         old_subpath = self._lookup(key)
  369.         temp_key = self.add(message)
  370.         temp_subpath = self._lookup(temp_key)
  371.         if isinstance(message, MaildirMessage):
  372.             dominant_subpath = temp_subpath
  373.         else:
  374.             dominant_subpath = old_subpath
  375.         subdir = os.path.dirname(dominant_subpath)
  376.         if self.colon in dominant_subpath:
  377.             suffix = self.colon + dominant_subpath.split(self.colon)[-1]
  378.         else:
  379.             suffix = ''
  380.         self.discard(key)
  381.         new_path = os.path.join(self._path, subdir, key + suffix)
  382.         os.rename(os.path.join(self._path, temp_subpath), new_path)
  383.         if isinstance(message, MaildirMessage):
  384.             os.utime(new_path, (os.path.getatime(new_path), message.get_date()))
  385.         
  386.  
  387.     
  388.     def get_message(self, key):
  389.         '''Return a Message representation or raise a KeyError.'''
  390.         subpath = self._lookup(key)
  391.         f = open(os.path.join(self._path, subpath), 'r')
  392.         
  393.         try:
  394.             if self._factory:
  395.                 msg = self._factory(f)
  396.             else:
  397.                 msg = MaildirMessage(f)
  398.         finally:
  399.             f.close()
  400.  
  401.         (subdir, name) = os.path.split(subpath)
  402.         msg.set_subdir(subdir)
  403.         if self.colon in name:
  404.             msg.set_info(name.split(self.colon)[-1])
  405.         
  406.         msg.set_date(os.path.getmtime(os.path.join(self._path, subpath)))
  407.         return msg
  408.  
  409.     
  410.     def get_string(self, key):
  411.         '''Return a string representation or raise a KeyError.'''
  412.         f = open(os.path.join(self._path, self._lookup(key)), 'r')
  413.         
  414.         try:
  415.             return f.read()
  416.         finally:
  417.             f.close()
  418.  
  419.  
  420.     
  421.     def get_file(self, key):
  422.         '''Return a file-like representation or raise a KeyError.'''
  423.         f = open(os.path.join(self._path, self._lookup(key)), 'rb')
  424.         return _ProxyFile(f)
  425.  
  426.     
  427.     def iterkeys(self):
  428.         '''Return an iterator over keys.'''
  429.         self._refresh()
  430.         for key in self._toc:
  431.             
  432.             try:
  433.                 self._lookup(key)
  434.             except KeyError:
  435.                 continue
  436.  
  437.             yield key
  438.         
  439.  
  440.     
  441.     def has_key(self, key):
  442.         '''Return True if the keyed message exists, False otherwise.'''
  443.         self._refresh()
  444.         return key in self._toc
  445.  
  446.     
  447.     def __len__(self):
  448.         '''Return a count of messages in the mailbox.'''
  449.         self._refresh()
  450.         return len(self._toc)
  451.  
  452.     
  453.     def flush(self):
  454.         '''Write any pending changes to disk.'''
  455.         pass
  456.  
  457.     
  458.     def lock(self):
  459.         '''Lock the mailbox.'''
  460.         pass
  461.  
  462.     
  463.     def unlock(self):
  464.         '''Unlock the mailbox if it is locked.'''
  465.         pass
  466.  
  467.     
  468.     def close(self):
  469.         '''Flush and close the mailbox.'''
  470.         pass
  471.  
  472.     
  473.     def list_folders(self):
  474.         '''Return a list of folder names.'''
  475.         result = []
  476.         for entry in os.listdir(self._path):
  477.             if len(entry) > 1 and entry[0] == '.' and os.path.isdir(os.path.join(self._path, entry)):
  478.                 result.append(entry[1:])
  479.                 continue
  480.         
  481.         return result
  482.  
  483.     
  484.     def get_folder(self, folder):
  485.         '''Return a Maildir instance for the named folder.'''
  486.         return Maildir(os.path.join(self._path, '.' + folder), factory = self._factory, create = False)
  487.  
  488.     
  489.     def add_folder(self, folder):
  490.         '''Create a folder and return a Maildir instance representing it.'''
  491.         path = os.path.join(self._path, '.' + folder)
  492.         result = Maildir(path, factory = self._factory)
  493.         maildirfolder_path = os.path.join(path, 'maildirfolder')
  494.         if not os.path.exists(maildirfolder_path):
  495.             os.close(os.open(maildirfolder_path, os.O_CREAT | os.O_WRONLY))
  496.         
  497.         return result
  498.  
  499.     
  500.     def remove_folder(self, folder):
  501.         '''Delete the named folder, which must be empty.'''
  502.         path = os.path.join(self._path, '.' + folder)
  503.         for entry in os.listdir(os.path.join(path, 'new')) + os.listdir(os.path.join(path, 'cur')):
  504.             if len(entry) < 1 or entry[0] != '.':
  505.                 raise NotEmptyError('Folder contains message(s): %s' % folder)
  506.                 continue
  507.         
  508.         for entry in os.listdir(path):
  509.             if entry != 'new' and entry != 'cur' and entry != 'tmp' and os.path.isdir(os.path.join(path, entry)):
  510.                 raise NotEmptyError("Folder contains subdirectory '%s': %s" % (folder, entry))
  511.                 continue
  512.         
  513.         for root, dirs, files in os.walk(path, topdown = False):
  514.             for entry in files:
  515.                 os.remove(os.path.join(root, entry))
  516.             
  517.             for entry in dirs:
  518.                 os.rmdir(os.path.join(root, entry))
  519.             
  520.         
  521.         os.rmdir(path)
  522.  
  523.     
  524.     def clean(self):
  525.         '''Delete old files in "tmp".'''
  526.         now = time.time()
  527.         for entry in os.listdir(os.path.join(self._path, 'tmp')):
  528.             path = os.path.join(self._path, 'tmp', entry)
  529.             if now - os.path.getatime(path) > 129600:
  530.                 os.remove(path)
  531.                 continue
  532.         
  533.  
  534.     _count = 1
  535.     
  536.     def _create_tmp(self):
  537.         '''Create a file in the tmp subdirectory and open and return it.'''
  538.         now = time.time()
  539.         hostname = socket.gethostname()
  540.         if '/' in hostname:
  541.             hostname = hostname.replace('/', '\\057')
  542.         
  543.         if ':' in hostname:
  544.             hostname = hostname.replace(':', '\\072')
  545.         
  546.         uniq = '%s.M%sP%sQ%s.%s' % (int(now), int((now % 1) * 1e+06), os.getpid(), Maildir._count, hostname)
  547.         path = os.path.join(self._path, 'tmp', uniq)
  548.         
  549.         try:
  550.             os.stat(path)
  551.         except OSError:
  552.             e = None
  553.             if e.errno == errno.ENOENT:
  554.                 Maildir._count += 1
  555.                 
  556.                 try:
  557.                     return _create_carefully(path)
  558.                 except OSError:
  559.                     Maildir
  560.                     e = Maildir
  561.                     if e.errno != errno.EEXIST:
  562.                         raise 
  563.                     
  564.                 except:
  565.                     e.errno != errno.EEXIST
  566.                 
  567.  
  568.             Maildir<EXCEPTION MATCH>OSError
  569.             raise 
  570.         except:
  571.             Maildir
  572.  
  573.         raise ExternalClashError('Name clash prevented file creation: %s' % path)
  574.  
  575.     
  576.     def _refresh(self):
  577.         '''Update table of contents mapping.'''
  578.         self._toc = { }
  579.         for subdir in ('new', 'cur'):
  580.             subdir_path = os.path.join(self._path, subdir)
  581.             for entry in os.listdir(subdir_path):
  582.                 p = os.path.join(subdir_path, entry)
  583.                 if os.path.isdir(p):
  584.                     continue
  585.                 
  586.                 uniq = entry.split(self.colon)[0]
  587.                 self._toc[uniq] = os.path.join(subdir, entry)
  588.             
  589.         
  590.  
  591.     
  592.     def _lookup(self, key):
  593.         '''Use TOC to return subpath for given key, or raise a KeyError.'''
  594.         
  595.         try:
  596.             if os.path.exists(os.path.join(self._path, self._toc[key])):
  597.                 return self._toc[key]
  598.         except KeyError:
  599.             pass
  600.  
  601.         self._refresh()
  602.         
  603.         try:
  604.             return self._toc[key]
  605.         except KeyError:
  606.             raise KeyError('No message with key: %s' % key)
  607.  
  608.  
  609.     
  610.     def next(self):
  611.         '''Return the next message in a one-time iteration.'''
  612.         if not hasattr(self, '_onetime_keys'):
  613.             self._onetime_keys = self.iterkeys()
  614.         
  615.         while True:
  616.             
  617.             try:
  618.                 return self[self._onetime_keys.next()]
  619.             continue
  620.             except StopIteration:
  621.                 return None
  622.                 continue
  623.                 except KeyError:
  624.                     continue
  625.                     continue
  626.                 
  627.                 None<EXCEPTION MATCH>KeyError
  628.             return None
  629.  
  630.  
  631.  
  632.  
  633. class _singlefileMailbox(Mailbox):
  634.     '''A single-file mailbox.'''
  635.     
  636.     def __init__(self, path, factory = None, create = True):
  637.         '''Initialize a single-file mailbox.'''
  638.         Mailbox.__init__(self, path, factory, create)
  639.         
  640.         try:
  641.             f = open(self._path, 'rb+')
  642.         except IOError:
  643.             e = None
  644.             if e.errno == errno.ENOENT:
  645.                 if create:
  646.                     f = open(self._path, 'wb+')
  647.                 else:
  648.                     raise NoSuchMailboxError(self._path)
  649.             elif e.errno == errno.EACCES:
  650.                 f = open(self._path, 'rb')
  651.             else:
  652.                 raise 
  653.         except:
  654.             e.errno == errno.ENOENT
  655.  
  656.         self._file = f
  657.         self._toc = None
  658.         self._next_key = 0
  659.         self._pending = False
  660.         self._locked = False
  661.  
  662.     
  663.     def add(self, message):
  664.         '''Add message and return assigned key.'''
  665.         self._lookup()
  666.         self._toc[self._next_key] = self._append_message(message)
  667.         self._next_key += 1
  668.         self._pending = True
  669.         return self._next_key - 1
  670.  
  671.     
  672.     def remove(self, key):
  673.         """Remove the keyed message; raise KeyError if it doesn't exist."""
  674.         self._lookup(key)
  675.         del self._toc[key]
  676.         self._pending = True
  677.  
  678.     
  679.     def __setitem__(self, key, message):
  680.         """Replace the keyed message; raise KeyError if it doesn't exist."""
  681.         self._lookup(key)
  682.         self._toc[key] = self._append_message(message)
  683.         self._pending = True
  684.  
  685.     
  686.     def iterkeys(self):
  687.         '''Return an iterator over keys.'''
  688.         self._lookup()
  689.         for key in self._toc.keys():
  690.             yield key
  691.         
  692.  
  693.     
  694.     def has_key(self, key):
  695.         '''Return True if the keyed message exists, False otherwise.'''
  696.         self._lookup()
  697.         return key in self._toc
  698.  
  699.     
  700.     def __len__(self):
  701.         '''Return a count of messages in the mailbox.'''
  702.         self._lookup()
  703.         return len(self._toc)
  704.  
  705.     
  706.     def lock(self):
  707.         '''Lock the mailbox.'''
  708.         if not self._locked:
  709.             _lock_file(self._file)
  710.             self._locked = True
  711.         
  712.  
  713.     
  714.     def unlock(self):
  715.         '''Unlock the mailbox if it is locked.'''
  716.         if self._locked:
  717.             _unlock_file(self._file)
  718.             self._locked = False
  719.         
  720.  
  721.     
  722.     def flush(self):
  723.         '''Write any pending changes to disk.'''
  724.         if not self._pending:
  725.             return None
  726.         
  727.         self._lookup()
  728.         new_file = _create_temporary(self._path)
  729.         
  730.         try:
  731.             new_toc = { }
  732.             self._pre_mailbox_hook(new_file)
  733.             for key in sorted(self._toc.keys()):
  734.                 (start, stop) = self._toc[key]
  735.                 self._file.seek(start)
  736.                 self._pre_message_hook(new_file)
  737.                 new_start = new_file.tell()
  738.                 while True:
  739.                     buffer = self._file.read(min(4096, stop - self._file.tell()))
  740.                     if buffer == '':
  741.                         break
  742.                     
  743.                     new_file.write(buffer)
  744.                 new_toc[key] = (new_start, new_file.tell())
  745.                 self._post_message_hook(new_file)
  746.         except:
  747.             new_file.close()
  748.             os.remove(new_file.name)
  749.             raise 
  750.  
  751.         _sync_close(new_file)
  752.         self._file.close()
  753.         
  754.         try:
  755.             os.rename(new_file.name, self._path)
  756.         except OSError:
  757.             e = None
  758.             if (e.errno == errno.EEXIST or os.name == 'os2') and e.errno == errno.EACCES:
  759.                 os.remove(self._path)
  760.                 os.rename(new_file.name, self._path)
  761.             else:
  762.                 raise 
  763.         except:
  764.             e.errno == errno.EACCES
  765.  
  766.         self._file = open(self._path, 'rb+')
  767.         self._toc = new_toc
  768.         self._pending = False
  769.         if self._locked:
  770.             _lock_file(self._file, dotlock = False)
  771.         
  772.  
  773.     
  774.     def _pre_mailbox_hook(self, f):
  775.         '''Called before writing the mailbox to file f.'''
  776.         pass
  777.  
  778.     
  779.     def _pre_message_hook(self, f):
  780.         '''Called before writing each message to file f.'''
  781.         pass
  782.  
  783.     
  784.     def _post_message_hook(self, f):
  785.         '''Called after writing each message to file f.'''
  786.         pass
  787.  
  788.     
  789.     def close(self):
  790.         '''Flush and close the mailbox.'''
  791.         self.flush()
  792.         if self._locked:
  793.             self.unlock()
  794.         
  795.         self._file.close()
  796.  
  797.     
  798.     def _lookup(self, key = None):
  799.         '''Return (start, stop) or raise KeyError.'''
  800.         if self._toc is None:
  801.             self._generate_toc()
  802.         
  803.         if key is not None:
  804.             
  805.             try:
  806.                 return self._toc[key]
  807.             except KeyError:
  808.                 raise KeyError('No message with key: %s' % key)
  809.             except:
  810.                 None<EXCEPTION MATCH>KeyError
  811.             
  812.  
  813.         None<EXCEPTION MATCH>KeyError
  814.  
  815.     
  816.     def _append_message(self, message):
  817.         '''Append message to mailbox and return (start, stop) offsets.'''
  818.         self._file.seek(0, 2)
  819.         self._pre_message_hook(self._file)
  820.         offsets = self._install_message(message)
  821.         self._post_message_hook(self._file)
  822.         self._file.flush()
  823.         return offsets
  824.  
  825.  
  826.  
  827. class _mboxMMDF(_singlefileMailbox):
  828.     '''An mbox or MMDF mailbox.'''
  829.     _mangle_from_ = True
  830.     
  831.     def get_message(self, key):
  832.         '''Return a Message representation or raise a KeyError.'''
  833.         (start, stop) = self._lookup(key)
  834.         self._file.seek(start)
  835.         from_line = self._file.readline().replace(os.linesep, '')
  836.         string = self._file.read(stop - self._file.tell())
  837.         msg = self._message_factory(string.replace(os.linesep, '\n'))
  838.         msg.set_from(from_line[5:])
  839.         return msg
  840.  
  841.     
  842.     def get_string(self, key, from_ = False):
  843.         '''Return a string representation or raise a KeyError.'''
  844.         (start, stop) = self._lookup(key)
  845.         self._file.seek(start)
  846.         if not from_:
  847.             self._file.readline()
  848.         
  849.         string = self._file.read(stop - self._file.tell())
  850.         return string.replace(os.linesep, '\n')
  851.  
  852.     
  853.     def get_file(self, key, from_ = False):
  854.         '''Return a file-like representation or raise a KeyError.'''
  855.         (start, stop) = self._lookup(key)
  856.         self._file.seek(start)
  857.         if not from_:
  858.             self._file.readline()
  859.         
  860.         return _PartialFile(self._file, self._file.tell(), stop)
  861.  
  862.     
  863.     def _install_message(self, message):
  864.         '''Format a message and blindly write to self._file.'''
  865.         from_line = None
  866.         if isinstance(message, str) and message.startswith('From '):
  867.             newline = message.find('\n')
  868.             if newline != -1:
  869.                 from_line = message[:newline]
  870.                 message = message[newline + 1:]
  871.             else:
  872.                 from_line = message
  873.                 message = ''
  874.         elif isinstance(message, _mboxMMDFMessage):
  875.             from_line = 'From ' + message.get_from()
  876.         elif isinstance(message, email.Message.Message):
  877.             from_line = message.get_unixfrom()
  878.         
  879.         if from_line is None:
  880.             from_line = 'From MAILER-DAEMON %s' % time.asctime(time.gmtime())
  881.         
  882.         start = self._file.tell()
  883.         self._file.write(from_line + os.linesep)
  884.         self._dump_message(message, self._file, self._mangle_from_)
  885.         stop = self._file.tell()
  886.         return (start, stop)
  887.  
  888.  
  889.  
  890. class mbox(_mboxMMDF):
  891.     '''A classic mbox mailbox.'''
  892.     _mangle_from_ = True
  893.     
  894.     def __init__(self, path, factory = None, create = True):
  895.         '''Initialize an mbox mailbox.'''
  896.         self._message_factory = mboxMessage
  897.         _mboxMMDF.__init__(self, path, factory, create)
  898.  
  899.     
  900.     def _pre_message_hook(self, f):
  901.         '''Called before writing each message to file f.'''
  902.         if f.tell() != 0:
  903.             f.write(os.linesep)
  904.         
  905.  
  906.     
  907.     def _generate_toc(self):
  908.         '''Generate key-to-(start, stop) table of contents.'''
  909.         starts = []
  910.         stops = []
  911.         self._file.seek(0)
  912.         while True:
  913.             line_pos = self._file.tell()
  914.             line = self._file.readline()
  915.             if line.startswith('From '):
  916.                 if len(stops) < len(starts):
  917.                     stops.append(line_pos - len(os.linesep))
  918.                 
  919.                 starts.append(line_pos)
  920.                 continue
  921.             if line == '':
  922.                 stops.append(line_pos)
  923.                 break
  924.                 continue
  925.         self._toc = dict(enumerate(zip(starts, stops)))
  926.         self._next_key = len(self._toc)
  927.  
  928.  
  929.  
  930. class MMDF(_mboxMMDF):
  931.     '''An MMDF mailbox.'''
  932.     
  933.     def __init__(self, path, factory = None, create = True):
  934.         '''Initialize an MMDF mailbox.'''
  935.         self._message_factory = MMDFMessage
  936.         _mboxMMDF.__init__(self, path, factory, create)
  937.  
  938.     
  939.     def _pre_message_hook(self, f):
  940.         '''Called before writing each message to file f.'''
  941.         f.write('\x01\x01\x01\x01' + os.linesep)
  942.  
  943.     
  944.     def _post_message_hook(self, f):
  945.         '''Called after writing each message to file f.'''
  946.         f.write(os.linesep + '\x01\x01\x01\x01' + os.linesep)
  947.  
  948.     
  949.     def _generate_toc(self):
  950.         '''Generate key-to-(start, stop) table of contents.'''
  951.         starts = []
  952.         stops = []
  953.         self._file.seek(0)
  954.         next_pos = 0
  955.         while True:
  956.             line_pos = next_pos
  957.             line = self._file.readline()
  958.             next_pos = self._file.tell()
  959.             if line.startswith('\x01\x01\x01\x01' + os.linesep):
  960.                 starts.append(next_pos)
  961.                 while True:
  962.                     line_pos = next_pos
  963.                     line = self._file.readline()
  964.                     next_pos = self._file.tell()
  965.                     if line == '\x01\x01\x01\x01' + os.linesep:
  966.                         stops.append(line_pos - len(os.linesep))
  967.                         break
  968.                         continue
  969.                     if line == '':
  970.                         stops.append(line_pos)
  971.                         break
  972.                         continue
  973.                 continue
  974.             if line == '':
  975.                 break
  976.                 continue
  977.         self._toc = dict(enumerate(zip(starts, stops)))
  978.         self._next_key = len(self._toc)
  979.  
  980.  
  981.  
  982. class MH(Mailbox):
  983.     '''An MH mailbox.'''
  984.     
  985.     def __init__(self, path, factory = None, create = True):
  986.         '''Initialize an MH instance.'''
  987.         Mailbox.__init__(self, path, factory, create)
  988.         if not os.path.exists(self._path):
  989.             if create:
  990.                 os.mkdir(self._path, 448)
  991.                 os.close(os.open(os.path.join(self._path, '.mh_sequences'), os.O_CREAT | os.O_EXCL | os.O_WRONLY, 384))
  992.             else:
  993.                 raise NoSuchMailboxError(self._path)
  994.         
  995.         self._locked = False
  996.  
  997.     
  998.     def add(self, message):
  999.         '''Add message and return assigned key.'''
  1000.         keys = self.keys()
  1001.         if len(keys) == 0:
  1002.             new_key = 1
  1003.         else:
  1004.             new_key = max(keys) + 1
  1005.         new_path = os.path.join(self._path, str(new_key))
  1006.         f = _create_carefully(new_path)
  1007.         
  1008.         try:
  1009.             if self._locked:
  1010.                 _lock_file(f)
  1011.             
  1012.             
  1013.             try:
  1014.                 self._dump_message(message, f)
  1015.                 if isinstance(message, MHMessage):
  1016.                     self._dump_sequences(message, new_key)
  1017.             finally:
  1018.                 if self._locked:
  1019.                     _unlock_file(f)
  1020.                 
  1021.  
  1022.         finally:
  1023.             _sync_close(f)
  1024.  
  1025.         return new_key
  1026.  
  1027.     
  1028.     def remove(self, key):
  1029.         """Remove the keyed message; raise KeyError if it doesn't exist."""
  1030.         path = os.path.join(self._path, str(key))
  1031.         
  1032.         try:
  1033.             f = open(path, 'rb+')
  1034.         except IOError:
  1035.             e = None
  1036.             if e.errno == errno.ENOENT:
  1037.                 raise KeyError('No message with key: %s' % key)
  1038.             else:
  1039.                 raise 
  1040.         except:
  1041.             e.errno == errno.ENOENT
  1042.  
  1043.         
  1044.         try:
  1045.             if self._locked:
  1046.                 _lock_file(f)
  1047.             
  1048.             
  1049.             try:
  1050.                 f.close()
  1051.                 os.remove(os.path.join(self._path, str(key)))
  1052.             finally:
  1053.                 if self._locked:
  1054.                     _unlock_file(f)
  1055.                 
  1056.  
  1057.         finally:
  1058.             f.close()
  1059.  
  1060.  
  1061.     
  1062.     def __setitem__(self, key, message):
  1063.         """Replace the keyed message; raise KeyError if it doesn't exist."""
  1064.         path = os.path.join(self._path, str(key))
  1065.         
  1066.         try:
  1067.             f = open(path, 'rb+')
  1068.         except IOError:
  1069.             e = None
  1070.             if e.errno == errno.ENOENT:
  1071.                 raise KeyError('No message with key: %s' % key)
  1072.             else:
  1073.                 raise 
  1074.         except:
  1075.             e.errno == errno.ENOENT
  1076.  
  1077.         
  1078.         try:
  1079.             if self._locked:
  1080.                 _lock_file(f)
  1081.             
  1082.             
  1083.             try:
  1084.                 os.close(os.open(path, os.O_WRONLY | os.O_TRUNC))
  1085.                 self._dump_message(message, f)
  1086.                 if isinstance(message, MHMessage):
  1087.                     self._dump_sequences(message, key)
  1088.             finally:
  1089.                 if self._locked:
  1090.                     _unlock_file(f)
  1091.                 
  1092.  
  1093.         finally:
  1094.             _sync_close(f)
  1095.  
  1096.  
  1097.     
  1098.     def get_message(self, key):
  1099.         '''Return a Message representation or raise a KeyError.'''
  1100.         
  1101.         try:
  1102.             if self._locked:
  1103.                 f = open(os.path.join(self._path, str(key)), 'r+')
  1104.             else:
  1105.                 f = open(os.path.join(self._path, str(key)), 'r')
  1106.         except IOError:
  1107.             e = None
  1108.             if e.errno == errno.ENOENT:
  1109.                 raise KeyError('No message with key: %s' % key)
  1110.             else:
  1111.                 raise 
  1112.         except:
  1113.             e.errno == errno.ENOENT
  1114.  
  1115.         
  1116.         try:
  1117.             if self._locked:
  1118.                 _lock_file(f)
  1119.             
  1120.             
  1121.             try:
  1122.                 msg = MHMessage(f)
  1123.             finally:
  1124.                 if self._locked:
  1125.                     _unlock_file(f)
  1126.                 
  1127.  
  1128.         finally:
  1129.             f.close()
  1130.  
  1131.         for name, key_list in self.get_sequences():
  1132.             if key in key_list:
  1133.                 msg.add_sequence(name)
  1134.                 continue
  1135.         
  1136.         return msg
  1137.  
  1138.     
  1139.     def get_string(self, key):
  1140.         '''Return a string representation or raise a KeyError.'''
  1141.         
  1142.         try:
  1143.             if self._locked:
  1144.                 f = open(os.path.join(self._path, str(key)), 'r+')
  1145.             else:
  1146.                 f = open(os.path.join(self._path, str(key)), 'r')
  1147.         except IOError:
  1148.             e = None
  1149.             if e.errno == errno.ENOENT:
  1150.                 raise KeyError('No message with key: %s' % key)
  1151.             else:
  1152.                 raise 
  1153.         except:
  1154.             e.errno == errno.ENOENT
  1155.  
  1156.         
  1157.         try:
  1158.             if self._locked:
  1159.                 _lock_file(f)
  1160.             
  1161.             
  1162.             try:
  1163.                 return f.read()
  1164.             finally:
  1165.                 if self._locked:
  1166.                     _unlock_file(f)
  1167.                 
  1168.  
  1169.         finally:
  1170.             f.close()
  1171.  
  1172.  
  1173.     
  1174.     def get_file(self, key):
  1175.         '''Return a file-like representation or raise a KeyError.'''
  1176.         
  1177.         try:
  1178.             f = open(os.path.join(self._path, str(key)), 'rb')
  1179.         except IOError:
  1180.             e = None
  1181.             if e.errno == errno.ENOENT:
  1182.                 raise KeyError('No message with key: %s' % key)
  1183.             else:
  1184.                 raise 
  1185.         except:
  1186.             e.errno == errno.ENOENT
  1187.  
  1188.         return _ProxyFile(f)
  1189.  
  1190.     
  1191.     def iterkeys(self):
  1192.         '''Return an iterator over keys.'''
  1193.         return iter(sorted((lambda .0: for entry in .0:
  1194. if entry.isdigit():
  1195. int(entry)continue)(os.listdir(self._path))))
  1196.  
  1197.     
  1198.     def has_key(self, key):
  1199.         '''Return True if the keyed message exists, False otherwise.'''
  1200.         return os.path.exists(os.path.join(self._path, str(key)))
  1201.  
  1202.     
  1203.     def __len__(self):
  1204.         '''Return a count of messages in the mailbox.'''
  1205.         return len(list(self.iterkeys()))
  1206.  
  1207.     
  1208.     def lock(self):
  1209.         '''Lock the mailbox.'''
  1210.         if not self._locked:
  1211.             self._file = open(os.path.join(self._path, '.mh_sequences'), 'rb+')
  1212.             _lock_file(self._file)
  1213.             self._locked = True
  1214.         
  1215.  
  1216.     
  1217.     def unlock(self):
  1218.         '''Unlock the mailbox if it is locked.'''
  1219.         if self._locked:
  1220.             _unlock_file(self._file)
  1221.             _sync_close(self._file)
  1222.             del self._file
  1223.             self._locked = False
  1224.         
  1225.  
  1226.     
  1227.     def flush(self):
  1228.         '''Write any pending changes to the disk.'''
  1229.         pass
  1230.  
  1231.     
  1232.     def close(self):
  1233.         '''Flush and close the mailbox.'''
  1234.         if self._locked:
  1235.             self.unlock()
  1236.         
  1237.  
  1238.     
  1239.     def list_folders(self):
  1240.         '''Return a list of folder names.'''
  1241.         result = []
  1242.         for entry in os.listdir(self._path):
  1243.             if os.path.isdir(os.path.join(self._path, entry)):
  1244.                 result.append(entry)
  1245.                 continue
  1246.         
  1247.         return result
  1248.  
  1249.     
  1250.     def get_folder(self, folder):
  1251.         '''Return an MH instance for the named folder.'''
  1252.         return MH(os.path.join(self._path, folder), factory = self._factory, create = False)
  1253.  
  1254.     
  1255.     def add_folder(self, folder):
  1256.         '''Create a folder and return an MH instance representing it.'''
  1257.         return MH(os.path.join(self._path, folder), factory = self._factory)
  1258.  
  1259.     
  1260.     def remove_folder(self, folder):
  1261.         '''Delete the named folder, which must be empty.'''
  1262.         path = os.path.join(self._path, folder)
  1263.         entries = os.listdir(path)
  1264.         if entries == [
  1265.             '.mh_sequences']:
  1266.             os.remove(os.path.join(path, '.mh_sequences'))
  1267.         elif entries == []:
  1268.             pass
  1269.         else:
  1270.             raise NotEmptyError('Folder not empty: %s' % self._path)
  1271.         os.rmdir(path)
  1272.  
  1273.     
  1274.     def get_sequences(self):
  1275.         '''Return a name-to-key-list dictionary to define each sequence.'''
  1276.         results = { }
  1277.         f = open(os.path.join(self._path, '.mh_sequences'), 'r')
  1278.         
  1279.         try:
  1280.             all_keys = set(self.keys())
  1281.             for line in f:
  1282.                 
  1283.                 try:
  1284.                     (name, contents) = line.split(':')
  1285.                     keys = set()
  1286.                     for spec in contents.split():
  1287.                         if spec.isdigit():
  1288.                             keys.add(int(spec))
  1289.                             continue
  1290.                         (start, stop) = (lambda .0: for x in .0:
  1291. int(x))(spec.split('-'))
  1292.                         keys.update(range(start, stop + 1))
  1293.                     
  1294.                     results[name] = _[1]
  1295.                     if len(results[name]) == 0:
  1296.                         del results[name]
  1297.                 continue
  1298.                 except ValueError:
  1299.                     raise FormatError('Invalid sequence specification: %s' % line.rstrip())
  1300.                     continue
  1301.                 
  1302.  
  1303.         finally:
  1304.             f.close()
  1305.  
  1306.         return results
  1307.  
  1308.     
  1309.     def set_sequences(self, sequences):
  1310.         '''Set sequences using the given name-to-key-list dictionary.'''
  1311.         f = open(os.path.join(self._path, '.mh_sequences'), 'r+')
  1312.         
  1313.         try:
  1314.             os.close(os.open(f.name, os.O_WRONLY | os.O_TRUNC))
  1315.             for name, keys in sequences.iteritems():
  1316.                 if len(keys) == 0:
  1317.                     continue
  1318.                 
  1319.                 f.write('%s:' % name)
  1320.                 prev = None
  1321.                 completing = False
  1322.                 for key in sorted(set(keys)):
  1323.                     if key - 1 == prev:
  1324.                         if not completing:
  1325.                             completing = True
  1326.                             f.write('-')
  1327.                         
  1328.                     elif completing:
  1329.                         completing = False
  1330.                         f.write('%s %s' % (prev, key))
  1331.                     else:
  1332.                         f.write(' %s' % key)
  1333.                     prev = key
  1334.                 
  1335.                 if completing:
  1336.                     f.write(str(prev) + '\n')
  1337.                     continue
  1338.                 f.write('\n')
  1339.         finally:
  1340.             _sync_close(f)
  1341.  
  1342.  
  1343.     
  1344.     def pack(self):
  1345.         '''Re-name messages to eliminate numbering gaps. Invalidates keys.'''
  1346.         sequences = self.get_sequences()
  1347.         prev = 0
  1348.         changes = []
  1349.         for key in self.iterkeys():
  1350.             if key - 1 != prev:
  1351.                 changes.append((key, prev + 1))
  1352.                 if hasattr(os, 'link'):
  1353.                     os.link(os.path.join(self._path, str(key)), os.path.join(self._path, str(prev + 1)))
  1354.                     os.unlink(os.path.join(self._path, str(key)))
  1355.                 else:
  1356.                     os.rename(os.path.join(self._path, str(key)), os.path.join(self._path, str(prev + 1)))
  1357.             
  1358.             prev += 1
  1359.         
  1360.         self._next_key = prev + 1
  1361.         if len(changes) == 0:
  1362.             return None
  1363.         
  1364.         for name, key_list in sequences.items():
  1365.             for old, new in changes:
  1366.                 if old in key_list:
  1367.                     key_list[key_list.index(old)] = new
  1368.                     continue
  1369.             
  1370.         
  1371.         self.set_sequences(sequences)
  1372.  
  1373.     
  1374.     def _dump_sequences(self, message, key):
  1375.         '''Inspect a new MHMessage and update sequences appropriately.'''
  1376.         pending_sequences = message.get_sequences()
  1377.         all_sequences = self.get_sequences()
  1378.         for name, key_list in all_sequences.iteritems():
  1379.             if name in pending_sequences:
  1380.                 key_list.append(key)
  1381.                 continue
  1382.             if key in key_list:
  1383.                 del key_list[key_list.index(key)]
  1384.                 continue
  1385.         
  1386.         for sequence in pending_sequences:
  1387.             if sequence not in all_sequences:
  1388.                 all_sequences[sequence] = [
  1389.                     key]
  1390.                 continue
  1391.         
  1392.         self.set_sequences(all_sequences)
  1393.  
  1394.  
  1395.  
  1396. class Babyl(_singlefileMailbox):
  1397.     '''An Rmail-style Babyl mailbox.'''
  1398.     _special_labels = frozenset(('unseen', 'deleted', 'filed', 'answered', 'forwarded', 'edited', 'resent'))
  1399.     
  1400.     def __init__(self, path, factory = None, create = True):
  1401.         '''Initialize a Babyl mailbox.'''
  1402.         _singlefileMailbox.__init__(self, path, factory, create)
  1403.         self._labels = { }
  1404.  
  1405.     
  1406.     def add(self, message):
  1407.         '''Add message and return assigned key.'''
  1408.         key = _singlefileMailbox.add(self, message)
  1409.         if isinstance(message, BabylMessage):
  1410.             self._labels[key] = message.get_labels()
  1411.         
  1412.         return key
  1413.  
  1414.     
  1415.     def remove(self, key):
  1416.         """Remove the keyed message; raise KeyError if it doesn't exist."""
  1417.         _singlefileMailbox.remove(self, key)
  1418.         if key in self._labels:
  1419.             del self._labels[key]
  1420.         
  1421.  
  1422.     
  1423.     def __setitem__(self, key, message):
  1424.         """Replace the keyed message; raise KeyError if it doesn't exist."""
  1425.         _singlefileMailbox.__setitem__(self, key, message)
  1426.         if isinstance(message, BabylMessage):
  1427.             self._labels[key] = message.get_labels()
  1428.         
  1429.  
  1430.     
  1431.     def get_message(self, key):
  1432.         '''Return a Message representation or raise a KeyError.'''
  1433.         (start, stop) = self._lookup(key)
  1434.         self._file.seek(start)
  1435.         self._file.readline()
  1436.         original_headers = StringIO.StringIO()
  1437.         while True:
  1438.             line = self._file.readline()
  1439.             if line == '*** EOOH ***' + os.linesep or line == '':
  1440.                 break
  1441.             
  1442.             original_headers.write(line.replace(os.linesep, '\n'))
  1443.         visible_headers = StringIO.StringIO()
  1444.         while True:
  1445.             line = self._file.readline()
  1446.             if line == os.linesep or line == '':
  1447.                 break
  1448.             
  1449.             visible_headers.write(line.replace(os.linesep, '\n'))
  1450.         body = self._file.read(stop - self._file.tell()).replace(os.linesep, '\n')
  1451.         msg = BabylMessage(original_headers.getvalue() + body)
  1452.         msg.set_visible(visible_headers.getvalue())
  1453.         if key in self._labels:
  1454.             msg.set_labels(self._labels[key])
  1455.         
  1456.         return msg
  1457.  
  1458.     
  1459.     def get_string(self, key):
  1460.         '''Return a string representation or raise a KeyError.'''
  1461.         (start, stop) = self._lookup(key)
  1462.         self._file.seek(start)
  1463.         self._file.readline()
  1464.         original_headers = StringIO.StringIO()
  1465.         while True:
  1466.             line = self._file.readline()
  1467.             if line == '*** EOOH ***' + os.linesep or line == '':
  1468.                 break
  1469.             
  1470.             original_headers.write(line.replace(os.linesep, '\n'))
  1471.         while True:
  1472.             line = self._file.readline()
  1473.             if line == os.linesep or line == '':
  1474.                 break
  1475.                 continue
  1476.         return original_headers.getvalue() + self._file.read(stop - self._file.tell()).replace(os.linesep, '\n')
  1477.  
  1478.     
  1479.     def get_file(self, key):
  1480.         '''Return a file-like representation or raise a KeyError.'''
  1481.         return StringIO.StringIO(self.get_string(key).replace('\n', os.linesep))
  1482.  
  1483.     
  1484.     def get_labels(self):
  1485.         '''Return a list of user-defined labels in the mailbox.'''
  1486.         self._lookup()
  1487.         labels = set()
  1488.         for label_list in self._labels.values():
  1489.             labels.update(label_list)
  1490.         
  1491.         labels.difference_update(self._special_labels)
  1492.         return list(labels)
  1493.  
  1494.     
  1495.     def _generate_toc(self):
  1496.         '''Generate key-to-(start, stop) table of contents.'''
  1497.         starts = []
  1498.         stops = []
  1499.         self._file.seek(0)
  1500.         next_pos = 0
  1501.         label_lists = []
  1502.         while True:
  1503.             line_pos = next_pos
  1504.             line = self._file.readline()
  1505.             next_pos = self._file.tell()
  1506.             if line == '\x1f\x0c' + os.linesep:
  1507.                 if len(stops) < len(starts):
  1508.                     stops.append(line_pos - len(os.linesep))
  1509.                 
  1510.                 starts.append(next_pos)
  1511.                 labels = _[1]
  1512.                 label_lists.append(labels)
  1513.                 continue
  1514.             []
  1515.             if line == '\x1f' or line == '\x1f' + os.linesep:
  1516.                 if len(stops) < len(starts):
  1517.                     stops.append(line_pos - len(os.linesep))
  1518.                 
  1519.             len(stops) < len(starts)
  1520.             if line == '':
  1521.                 stops.append(line_pos - len(os.linesep))
  1522.                 break
  1523.                 continue
  1524.             []
  1525.         self._toc = dict(enumerate(zip(starts, stops)))
  1526.         self._labels = dict(enumerate(label_lists))
  1527.         self._next_key = len(self._toc)
  1528.  
  1529.     
  1530.     def _pre_mailbox_hook(self, f):
  1531.         '''Called before writing the mailbox to file f.'''
  1532.         f.write('BABYL OPTIONS:%sVersion: 5%sLabels:%s%s\x1f' % (os.linesep, os.linesep, ','.join(self.get_labels()), os.linesep))
  1533.  
  1534.     
  1535.     def _pre_message_hook(self, f):
  1536.         '''Called before writing each message to file f.'''
  1537.         f.write('\x0c' + os.linesep)
  1538.  
  1539.     
  1540.     def _post_message_hook(self, f):
  1541.         '''Called after writing each message to file f.'''
  1542.         f.write(os.linesep + '\x1f')
  1543.  
  1544.     
  1545.     def _install_message(self, message):
  1546.         '''Write message contents and return (start, stop).'''
  1547.         start = self._file.tell()
  1548.         if isinstance(message, BabylMessage):
  1549.             special_labels = []
  1550.             labels = []
  1551.             for label in message.get_labels():
  1552.                 if label in self._special_labels:
  1553.                     special_labels.append(label)
  1554.                     continue
  1555.                 labels.append(label)
  1556.             
  1557.             self._file.write('1')
  1558.             for label in special_labels:
  1559.                 self._file.write(', ' + label)
  1560.             
  1561.             self._file.write(',,')
  1562.             for label in labels:
  1563.                 self._file.write(' ' + label + ',')
  1564.             
  1565.             self._file.write(os.linesep)
  1566.         else:
  1567.             self._file.write('1,,' + os.linesep)
  1568.         if isinstance(message, email.Message.Message):
  1569.             orig_buffer = StringIO.StringIO()
  1570.             orig_generator = email.Generator.Generator(orig_buffer, False, 0)
  1571.             orig_generator.flatten(message)
  1572.             orig_buffer.seek(0)
  1573.             while True:
  1574.                 line = orig_buffer.readline()
  1575.                 self._file.write(line.replace('\n', os.linesep))
  1576.                 if line == '\n' or line == '':
  1577.                     break
  1578.                     continue
  1579.             self._file.write('*** EOOH ***' + os.linesep)
  1580.             if isinstance(message, BabylMessage):
  1581.                 vis_buffer = StringIO.StringIO()
  1582.                 vis_generator = email.Generator.Generator(vis_buffer, False, 0)
  1583.                 vis_generator.flatten(message.get_visible())
  1584.                 while True:
  1585.                     line = vis_buffer.readline()
  1586.                     self._file.write(line.replace('\n', os.linesep))
  1587.                     if line == '\n' or line == '':
  1588.                         break
  1589.                         continue
  1590.             else:
  1591.                 orig_buffer.seek(0)
  1592.                 while True:
  1593.                     line = orig_buffer.readline()
  1594.                     self._file.write(line.replace('\n', os.linesep))
  1595.                     if line == '\n' or line == '':
  1596.                         break
  1597.                         continue
  1598.             while True:
  1599.                 buffer = orig_buffer.read(4096)
  1600.                 if buffer == '':
  1601.                     break
  1602.                 
  1603.                 self._file.write(buffer.replace('\n', os.linesep))
  1604.         elif isinstance(message, str):
  1605.             body_start = message.find('\n\n') + 2
  1606.             if body_start - 2 != -1:
  1607.                 self._file.write(message[:body_start].replace('\n', os.linesep))
  1608.                 self._file.write('*** EOOH ***' + os.linesep)
  1609.                 self._file.write(message[:body_start].replace('\n', os.linesep))
  1610.                 self._file.write(message[body_start:].replace('\n', os.linesep))
  1611.             else:
  1612.                 self._file.write('*** EOOH ***' + os.linesep + os.linesep)
  1613.                 self._file.write(message.replace('\n', os.linesep))
  1614.         elif hasattr(message, 'readline'):
  1615.             original_pos = message.tell()
  1616.             first_pass = True
  1617.             while True:
  1618.                 line = message.readline()
  1619.                 self._file.write(line.replace('\n', os.linesep))
  1620.                 if line == '\n' or line == '':
  1621.                     self._file.write('*** EOOH ***' + os.linesep)
  1622.                     if first_pass:
  1623.                         first_pass = False
  1624.                         message.seek(original_pos)
  1625.                     else:
  1626.                         break
  1627.                 first_pass
  1628.             while True:
  1629.                 buffer = message.read(4096)
  1630.                 if buffer == '':
  1631.                     break
  1632.                 
  1633.                 self._file.write(buffer.replace('\n', os.linesep))
  1634.         else:
  1635.             raise TypeError('Invalid message type: %s' % type(message))
  1636.         stop = self._file.tell()
  1637.         return (start, stop)
  1638.  
  1639.  
  1640.  
  1641. class Message(email.Message.Message):
  1642.     '''Message with mailbox-format-specific properties.'''
  1643.     
  1644.     def __init__(self, message = None):
  1645.         '''Initialize a Message instance.'''
  1646.         if isinstance(message, email.Message.Message):
  1647.             self._become_message(copy.deepcopy(message))
  1648.             if isinstance(message, Message):
  1649.                 message._explain_to(self)
  1650.             
  1651.         elif isinstance(message, str):
  1652.             self._become_message(email.message_from_string(message))
  1653.         elif hasattr(message, 'read'):
  1654.             self._become_message(email.message_from_file(message))
  1655.         elif message is None:
  1656.             email.Message.Message.__init__(self)
  1657.         else:
  1658.             raise TypeError('Invalid message type: %s' % type(message))
  1659.  
  1660.     
  1661.     def _become_message(self, message):
  1662.         '''Assume the non-format-specific state of message.'''
  1663.         for name in ('_headers', '_unixfrom', '_payload', '_charset', 'preamble', 'epilogue', 'defects', '_default_type'):
  1664.             self.__dict__[name] = message.__dict__[name]
  1665.         
  1666.  
  1667.     
  1668.     def _explain_to(self, message):
  1669.         '''Copy format-specific state to message insofar as possible.'''
  1670.         if isinstance(message, Message):
  1671.             return None
  1672.         else:
  1673.             raise TypeError('Cannot convert to specified type')
  1674.  
  1675.  
  1676.  
  1677. class MaildirMessage(Message):
  1678.     '''Message with Maildir-specific properties.'''
  1679.     
  1680.     def __init__(self, message = None):
  1681.         '''Initialize a MaildirMessage instance.'''
  1682.         self._subdir = 'new'
  1683.         self._info = ''
  1684.         self._date = time.time()
  1685.         Message.__init__(self, message)
  1686.  
  1687.     
  1688.     def get_subdir(self):
  1689.         """Return 'new' or 'cur'."""
  1690.         return self._subdir
  1691.  
  1692.     
  1693.     def set_subdir(self, subdir):
  1694.         """Set subdir to 'new' or 'cur'."""
  1695.         if subdir == 'new' or subdir == 'cur':
  1696.             self._subdir = subdir
  1697.         else:
  1698.             raise ValueError("subdir must be 'new' or 'cur': %s" % subdir)
  1699.  
  1700.     
  1701.     def get_flags(self):
  1702.         '''Return as a string the flags that are set.'''
  1703.         if self._info.startswith('2,'):
  1704.             return self._info[2:]
  1705.         else:
  1706.             return ''
  1707.  
  1708.     
  1709.     def set_flags(self, flags):
  1710.         '''Set the given flags and unset all others.'''
  1711.         self._info = '2,' + ''.join(sorted(flags))
  1712.  
  1713.     
  1714.     def add_flag(self, flag):
  1715.         '''Set the given flag(s) without changing others.'''
  1716.         self.set_flags(''.join(set(self.get_flags()) | set(flag)))
  1717.  
  1718.     
  1719.     def remove_flag(self, flag):
  1720.         '''Unset the given string flag(s) without changing others.'''
  1721.         if self.get_flags() != '':
  1722.             self.set_flags(''.join(set(self.get_flags()) - set(flag)))
  1723.         
  1724.  
  1725.     
  1726.     def get_date(self):
  1727.         '''Return delivery date of message, in seconds since the epoch.'''
  1728.         return self._date
  1729.  
  1730.     
  1731.     def set_date(self, date):
  1732.         '''Set delivery date of message, in seconds since the epoch.'''
  1733.         
  1734.         try:
  1735.             self._date = float(date)
  1736.         except ValueError:
  1737.             raise TypeError("can't convert to float: %s" % date)
  1738.  
  1739.  
  1740.     
  1741.     def get_info(self):
  1742.         '''Get the message\'s "info" as a string.'''
  1743.         return self._info
  1744.  
  1745.     
  1746.     def set_info(self, info):
  1747.         '''Set the message\'s "info" string.'''
  1748.         if isinstance(info, str):
  1749.             self._info = info
  1750.         else:
  1751.             raise TypeError('info must be a string: %s' % type(info))
  1752.  
  1753.     
  1754.     def _explain_to(self, message):
  1755.         '''Copy Maildir-specific state to message insofar as possible.'''
  1756.         if isinstance(message, MaildirMessage):
  1757.             message.set_flags(self.get_flags())
  1758.             message.set_subdir(self.get_subdir())
  1759.             message.set_date(self.get_date())
  1760.         elif isinstance(message, _mboxMMDFMessage):
  1761.             flags = set(self.get_flags())
  1762.             if 'S' in flags:
  1763.                 message.add_flag('R')
  1764.             
  1765.             if self.get_subdir() == 'cur':
  1766.                 message.add_flag('O')
  1767.             
  1768.             if 'T' in flags:
  1769.                 message.add_flag('D')
  1770.             
  1771.             if 'F' in flags:
  1772.                 message.add_flag('F')
  1773.             
  1774.             if 'R' in flags:
  1775.                 message.add_flag('A')
  1776.             
  1777.             message.set_from('MAILER-DAEMON', time.gmtime(self.get_date()))
  1778.         elif isinstance(message, MHMessage):
  1779.             flags = set(self.get_flags())
  1780.             if 'S' not in flags:
  1781.                 message.add_sequence('unseen')
  1782.             
  1783.             if 'R' in flags:
  1784.                 message.add_sequence('replied')
  1785.             
  1786.             if 'F' in flags:
  1787.                 message.add_sequence('flagged')
  1788.             
  1789.         elif isinstance(message, BabylMessage):
  1790.             flags = set(self.get_flags())
  1791.             if 'S' not in flags:
  1792.                 message.add_label('unseen')
  1793.             
  1794.             if 'T' in flags:
  1795.                 message.add_label('deleted')
  1796.             
  1797.             if 'R' in flags:
  1798.                 message.add_label('answered')
  1799.             
  1800.             if 'P' in flags:
  1801.                 message.add_label('forwarded')
  1802.             
  1803.         elif isinstance(message, Message):
  1804.             pass
  1805.         else:
  1806.             raise TypeError('Cannot convert to specified type: %s' % type(message))
  1807.  
  1808.  
  1809.  
  1810. class _mboxMMDFMessage(Message):
  1811.     '''Message with mbox- or MMDF-specific properties.'''
  1812.     
  1813.     def __init__(self, message = None):
  1814.         '''Initialize an mboxMMDFMessage instance.'''
  1815.         self.set_from('MAILER-DAEMON', True)
  1816.         if isinstance(message, email.Message.Message):
  1817.             unixfrom = message.get_unixfrom()
  1818.             if unixfrom is not None and unixfrom.startswith('From '):
  1819.                 self.set_from(unixfrom[5:])
  1820.             
  1821.         
  1822.         Message.__init__(self, message)
  1823.  
  1824.     
  1825.     def get_from(self):
  1826.         '''Return contents of "From " line.'''
  1827.         return self._from
  1828.  
  1829.     
  1830.     def set_from(self, from_, time_ = None):
  1831.         '''Set "From " line, formatting and appending time_ if specified.'''
  1832.         if time_ is not None:
  1833.             if time_ is True:
  1834.                 time_ = time.gmtime()
  1835.             
  1836.             from_ += ' ' + time.asctime(time_)
  1837.         
  1838.         self._from = from_
  1839.  
  1840.     
  1841.     def get_flags(self):
  1842.         '''Return as a string the flags that are set.'''
  1843.         return self.get('Status', '') + self.get('X-Status', '')
  1844.  
  1845.     
  1846.     def set_flags(self, flags):
  1847.         '''Set the given flags and unset all others.'''
  1848.         flags = set(flags)
  1849.         (status_flags, xstatus_flags) = ('', '')
  1850.         for flag in ('R', 'O'):
  1851.             if flag in flags:
  1852.                 status_flags += flag
  1853.                 flags.remove(flag)
  1854.                 continue
  1855.         
  1856.         for flag in ('D', 'F', 'A'):
  1857.             if flag in flags:
  1858.                 xstatus_flags += flag
  1859.                 flags.remove(flag)
  1860.                 continue
  1861.         
  1862.         xstatus_flags += ''.join(sorted(flags))
  1863.         
  1864.         try:
  1865.             self.replace_header('Status', status_flags)
  1866.         except KeyError:
  1867.             self.add_header('Status', status_flags)
  1868.  
  1869.         
  1870.         try:
  1871.             self.replace_header('X-Status', xstatus_flags)
  1872.         except KeyError:
  1873.             self.add_header('X-Status', xstatus_flags)
  1874.  
  1875.  
  1876.     
  1877.     def add_flag(self, flag):
  1878.         '''Set the given flag(s) without changing others.'''
  1879.         self.set_flags(''.join(set(self.get_flags()) | set(flag)))
  1880.  
  1881.     
  1882.     def remove_flag(self, flag):
  1883.         '''Unset the given string flag(s) without changing others.'''
  1884.         if 'Status' in self or 'X-Status' in self:
  1885.             self.set_flags(''.join(set(self.get_flags()) - set(flag)))
  1886.         
  1887.  
  1888.     
  1889.     def _explain_to(self, message):
  1890.         '''Copy mbox- or MMDF-specific state to message insofar as possible.'''
  1891.         if isinstance(message, MaildirMessage):
  1892.             flags = set(self.get_flags())
  1893.             if 'O' in flags:
  1894.                 message.set_subdir('cur')
  1895.             
  1896.             if 'F' in flags:
  1897.                 message.add_flag('F')
  1898.             
  1899.             if 'A' in flags:
  1900.                 message.add_flag('R')
  1901.             
  1902.             if 'R' in flags:
  1903.                 message.add_flag('S')
  1904.             
  1905.             if 'D' in flags:
  1906.                 message.add_flag('T')
  1907.             
  1908.             del message['status']
  1909.             del message['x-status']
  1910.             maybe_date = ' '.join(self.get_from().split()[-5:])
  1911.             
  1912.             try:
  1913.                 message.set_date(calendar.timegm(time.strptime(maybe_date, '%a %b %d %H:%M:%S %Y')))
  1914.             except (ValueError, OverflowError):
  1915.                 pass
  1916.             except:
  1917.                 None<EXCEPTION MATCH>(ValueError, OverflowError)
  1918.             
  1919.  
  1920.         None<EXCEPTION MATCH>(ValueError, OverflowError)
  1921.         if isinstance(message, _mboxMMDFMessage):
  1922.             message.set_flags(self.get_flags())
  1923.             message.set_from(self.get_from())
  1924.         elif isinstance(message, MHMessage):
  1925.             flags = set(self.get_flags())
  1926.             if 'R' not in flags:
  1927.                 message.add_sequence('unseen')
  1928.             
  1929.             if 'A' in flags:
  1930.                 message.add_sequence('replied')
  1931.             
  1932.             if 'F' in flags:
  1933.                 message.add_sequence('flagged')
  1934.             
  1935.             del message['status']
  1936.             del message['x-status']
  1937.         elif isinstance(message, BabylMessage):
  1938.             flags = set(self.get_flags())
  1939.             if 'R' not in flags:
  1940.                 message.add_label('unseen')
  1941.             
  1942.             if 'D' in flags:
  1943.                 message.add_label('deleted')
  1944.             
  1945.             if 'A' in flags:
  1946.                 message.add_label('answered')
  1947.             
  1948.             del message['status']
  1949.             del message['x-status']
  1950.         elif isinstance(message, Message):
  1951.             pass
  1952.         else:
  1953.             raise TypeError('Cannot convert to specified type: %s' % type(message))
  1954.  
  1955.  
  1956.  
  1957. class mboxMessage(_mboxMMDFMessage):
  1958.     '''Message with mbox-specific properties.'''
  1959.     pass
  1960.  
  1961.  
  1962. class MHMessage(Message):
  1963.     '''Message with MH-specific properties.'''
  1964.     
  1965.     def __init__(self, message = None):
  1966.         '''Initialize an MHMessage instance.'''
  1967.         self._sequences = []
  1968.         Message.__init__(self, message)
  1969.  
  1970.     
  1971.     def get_sequences(self):
  1972.         '''Return a list of sequences that include the message.'''
  1973.         return self._sequences[:]
  1974.  
  1975.     
  1976.     def set_sequences(self, sequences):
  1977.         '''Set the list of sequences that include the message.'''
  1978.         self._sequences = list(sequences)
  1979.  
  1980.     
  1981.     def add_sequence(self, sequence):
  1982.         '''Add sequence to list of sequences including the message.'''
  1983.         if isinstance(sequence, str):
  1984.             if sequence not in self._sequences:
  1985.                 self._sequences.append(sequence)
  1986.             
  1987.         else:
  1988.             raise TypeError('sequence must be a string: %s' % type(sequence))
  1989.  
  1990.     
  1991.     def remove_sequence(self, sequence):
  1992.         '''Remove sequence from the list of sequences including the message.'''
  1993.         
  1994.         try:
  1995.             self._sequences.remove(sequence)
  1996.         except ValueError:
  1997.             pass
  1998.  
  1999.  
  2000.     
  2001.     def _explain_to(self, message):
  2002.         '''Copy MH-specific state to message insofar as possible.'''
  2003.         if isinstance(message, MaildirMessage):
  2004.             sequences = set(self.get_sequences())
  2005.             if 'unseen' in sequences:
  2006.                 message.set_subdir('cur')
  2007.             else:
  2008.                 message.set_subdir('cur')
  2009.                 message.add_flag('S')
  2010.             if 'flagged' in sequences:
  2011.                 message.add_flag('F')
  2012.             
  2013.             if 'replied' in sequences:
  2014.                 message.add_flag('R')
  2015.             
  2016.         elif isinstance(message, _mboxMMDFMessage):
  2017.             sequences = set(self.get_sequences())
  2018.             if 'unseen' not in sequences:
  2019.                 message.add_flag('RO')
  2020.             else:
  2021.                 message.add_flag('O')
  2022.             if 'flagged' in sequences:
  2023.                 message.add_flag('F')
  2024.             
  2025.             if 'replied' in sequences:
  2026.                 message.add_flag('A')
  2027.             
  2028.         elif isinstance(message, MHMessage):
  2029.             for sequence in self.get_sequences():
  2030.                 message.add_sequence(sequence)
  2031.             
  2032.         elif isinstance(message, BabylMessage):
  2033.             sequences = set(self.get_sequences())
  2034.             if 'unseen' in sequences:
  2035.                 message.add_label('unseen')
  2036.             
  2037.             if 'replied' in sequences:
  2038.                 message.add_label('answered')
  2039.             
  2040.         elif isinstance(message, Message):
  2041.             pass
  2042.         else:
  2043.             raise TypeError('Cannot convert to specified type: %s' % type(message))
  2044.  
  2045.  
  2046.  
  2047. class BabylMessage(Message):
  2048.     '''Message with Babyl-specific properties.'''
  2049.     
  2050.     def __init__(self, message = None):
  2051.         '''Initialize an BabylMessage instance.'''
  2052.         self._labels = []
  2053.         self._visible = Message()
  2054.         Message.__init__(self, message)
  2055.  
  2056.     
  2057.     def get_labels(self):
  2058.         '''Return a list of labels on the message.'''
  2059.         return self._labels[:]
  2060.  
  2061.     
  2062.     def set_labels(self, labels):
  2063.         '''Set the list of labels on the message.'''
  2064.         self._labels = list(labels)
  2065.  
  2066.     
  2067.     def add_label(self, label):
  2068.         '''Add label to list of labels on the message.'''
  2069.         if isinstance(label, str):
  2070.             if label not in self._labels:
  2071.                 self._labels.append(label)
  2072.             
  2073.         else:
  2074.             raise TypeError('label must be a string: %s' % type(label))
  2075.  
  2076.     
  2077.     def remove_label(self, label):
  2078.         '''Remove label from the list of labels on the message.'''
  2079.         
  2080.         try:
  2081.             self._labels.remove(label)
  2082.         except ValueError:
  2083.             pass
  2084.  
  2085.  
  2086.     
  2087.     def get_visible(self):
  2088.         '''Return a Message representation of visible headers.'''
  2089.         return Message(self._visible)
  2090.  
  2091.     
  2092.     def set_visible(self, visible):
  2093.         '''Set the Message representation of visible headers.'''
  2094.         self._visible = Message(visible)
  2095.  
  2096.     
  2097.     def update_visible(self):
  2098.         '''Update and/or sensibly generate a set of visible headers.'''
  2099.         for header in self._visible.keys():
  2100.             if header in self:
  2101.                 self._visible.replace_header(header, self[header])
  2102.                 continue
  2103.             del self._visible[header]
  2104.         
  2105.         for header in ('Date', 'From', 'Reply-To', 'To', 'CC', 'Subject'):
  2106.             if header in self and header not in self._visible:
  2107.                 self._visible[header] = self[header]
  2108.                 continue
  2109.         
  2110.  
  2111.     
  2112.     def _explain_to(self, message):
  2113.         '''Copy Babyl-specific state to message insofar as possible.'''
  2114.         if isinstance(message, MaildirMessage):
  2115.             labels = set(self.get_labels())
  2116.             if 'unseen' in labels:
  2117.                 message.set_subdir('cur')
  2118.             else:
  2119.                 message.set_subdir('cur')
  2120.                 message.add_flag('S')
  2121.             if 'forwarded' in labels or 'resent' in labels:
  2122.                 message.add_flag('P')
  2123.             
  2124.             if 'answered' in labels:
  2125.                 message.add_flag('R')
  2126.             
  2127.             if 'deleted' in labels:
  2128.                 message.add_flag('T')
  2129.             
  2130.         elif isinstance(message, _mboxMMDFMessage):
  2131.             labels = set(self.get_labels())
  2132.             if 'unseen' not in labels:
  2133.                 message.add_flag('RO')
  2134.             else:
  2135.                 message.add_flag('O')
  2136.             if 'deleted' in labels:
  2137.                 message.add_flag('D')
  2138.             
  2139.             if 'answered' in labels:
  2140.                 message.add_flag('A')
  2141.             
  2142.         elif isinstance(message, MHMessage):
  2143.             labels = set(self.get_labels())
  2144.             if 'unseen' in labels:
  2145.                 message.add_sequence('unseen')
  2146.             
  2147.             if 'answered' in labels:
  2148.                 message.add_sequence('replied')
  2149.             
  2150.         elif isinstance(message, BabylMessage):
  2151.             message.set_visible(self.get_visible())
  2152.             for label in self.get_labels():
  2153.                 message.add_label(label)
  2154.             
  2155.         elif isinstance(message, Message):
  2156.             pass
  2157.         else:
  2158.             raise TypeError('Cannot convert to specified type: %s' % type(message))
  2159.  
  2160.  
  2161.  
  2162. class MMDFMessage(_mboxMMDFMessage):
  2163.     '''Message with MMDF-specific properties.'''
  2164.     pass
  2165.  
  2166.  
  2167. class _ProxyFile:
  2168.     '''A read-only wrapper of a file.'''
  2169.     
  2170.     def __init__(self, f, pos = None):
  2171.         '''Initialize a _ProxyFile.'''
  2172.         self._file = f
  2173.         if pos is None:
  2174.             self._pos = f.tell()
  2175.         else:
  2176.             self._pos = pos
  2177.  
  2178.     
  2179.     def read(self, size = None):
  2180.         '''Read bytes.'''
  2181.         return self._read(size, self._file.read)
  2182.  
  2183.     
  2184.     def readline(self, size = None):
  2185.         '''Read a line.'''
  2186.         return self._read(size, self._file.readline)
  2187.  
  2188.     
  2189.     def readlines(self, sizehint = None):
  2190.         '''Read multiple lines.'''
  2191.         result = []
  2192.         for line in self:
  2193.             result.append(line)
  2194.             if sizehint is not None:
  2195.                 sizehint -= len(line)
  2196.                 if sizehint <= 0:
  2197.                     break
  2198.                 
  2199.             sizehint <= 0
  2200.         
  2201.         return result
  2202.  
  2203.     
  2204.     def __iter__(self):
  2205.         '''Iterate over lines.'''
  2206.         return iter(self.readline, '')
  2207.  
  2208.     
  2209.     def tell(self):
  2210.         '''Return the position.'''
  2211.         return self._pos
  2212.  
  2213.     
  2214.     def seek(self, offset, whence = 0):
  2215.         '''Change position.'''
  2216.         if whence == 1:
  2217.             self._file.seek(self._pos)
  2218.         
  2219.         self._file.seek(offset, whence)
  2220.         self._pos = self._file.tell()
  2221.  
  2222.     
  2223.     def close(self):
  2224.         '''Close the file.'''
  2225.         del self._file
  2226.  
  2227.     
  2228.     def _read(self, size, read_method):
  2229.         '''Read size bytes using read_method.'''
  2230.         if size is None:
  2231.             size = -1
  2232.         
  2233.         self._file.seek(self._pos)
  2234.         result = read_method(size)
  2235.         self._pos = self._file.tell()
  2236.         return result
  2237.  
  2238.  
  2239.  
  2240. class _PartialFile(_ProxyFile):
  2241.     '''A read-only wrapper of part of a file.'''
  2242.     
  2243.     def __init__(self, f, start = None, stop = None):
  2244.         '''Initialize a _PartialFile.'''
  2245.         _ProxyFile.__init__(self, f, start)
  2246.         self._start = start
  2247.         self._stop = stop
  2248.  
  2249.     
  2250.     def tell(self):
  2251.         '''Return the position with respect to start.'''
  2252.         return _ProxyFile.tell(self) - self._start
  2253.  
  2254.     
  2255.     def seek(self, offset, whence = 0):
  2256.         '''Change position, possibly with respect to start or stop.'''
  2257.         if whence == 0:
  2258.             self._pos = self._start
  2259.             whence = 1
  2260.         elif whence == 2:
  2261.             self._pos = self._stop
  2262.             whence = 1
  2263.         
  2264.         _ProxyFile.seek(self, offset, whence)
  2265.  
  2266.     
  2267.     def _read(self, size, read_method):
  2268.         '''Read size bytes using read_method, honoring start and stop.'''
  2269.         remaining = self._stop - self._pos
  2270.         if remaining <= 0:
  2271.             return ''
  2272.         
  2273.         if size is None and size < 0 or size > remaining:
  2274.             size = remaining
  2275.         
  2276.         return _ProxyFile._read(self, size, read_method)
  2277.  
  2278.  
  2279.  
  2280. def _lock_file(f, dotlock = True):
  2281.     '''Lock file f using lockf and dot locking.'''
  2282.     dotlock_done = False
  2283.     
  2284.     try:
  2285.         if fcntl:
  2286.             
  2287.             try:
  2288.                 fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
  2289.             except IOError:
  2290.                 e = None
  2291.                 if e.errno in (errno.EAGAIN, errno.EACCES):
  2292.                     raise ExternalClashError('lockf: lock unavailable: %s' % f.name)
  2293.                 else:
  2294.                     raise 
  2295.             except:
  2296.                 e.errno in (errno.EAGAIN, errno.EACCES)
  2297.             
  2298.  
  2299.         None<EXCEPTION MATCH>IOError
  2300.         if dotlock:
  2301.             
  2302.             try:
  2303.                 pre_lock = _create_temporary(f.name + '.lock')
  2304.                 pre_lock.close()
  2305.             except IOError:
  2306.                 e = None
  2307.                 if e.errno == errno.EACCES:
  2308.                     return None
  2309.                 else:
  2310.                     raise 
  2311.             except:
  2312.                 e.errno == errno.EACCES
  2313.  
  2314.             
  2315.             try:
  2316.                 if hasattr(os, 'link'):
  2317.                     os.link(pre_lock.name, f.name + '.lock')
  2318.                     dotlock_done = True
  2319.                     os.unlink(pre_lock.name)
  2320.                 else:
  2321.                     os.rename(pre_lock.name, f.name + '.lock')
  2322.                     dotlock_done = True
  2323.             except OSError:
  2324.                 e = None
  2325.                 if (e.errno == errno.EEXIST or os.name == 'os2') and e.errno == errno.EACCES:
  2326.                     os.remove(pre_lock.name)
  2327.                     raise ExternalClashError('dot lock unavailable: %s' % f.name)
  2328.                 else:
  2329.                     raise 
  2330.             except:
  2331.                 e.errno == errno.EACCES
  2332.             
  2333.  
  2334.         None<EXCEPTION MATCH>OSError
  2335.     except:
  2336.         if fcntl:
  2337.             fcntl.lockf(f, fcntl.LOCK_UN)
  2338.         
  2339.         if dotlock_done:
  2340.             os.remove(f.name + '.lock')
  2341.         
  2342.         raise 
  2343.  
  2344.  
  2345.  
  2346. def _unlock_file(f):
  2347.     '''Unlock file f using lockf and dot locking.'''
  2348.     if fcntl:
  2349.         fcntl.lockf(f, fcntl.LOCK_UN)
  2350.     
  2351.     if os.path.exists(f.name + '.lock'):
  2352.         os.remove(f.name + '.lock')
  2353.     
  2354.  
  2355.  
  2356. def _create_carefully(path):
  2357.     """Create a file if it doesn't exist and open for reading and writing."""
  2358.     fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
  2359.     
  2360.     try:
  2361.         return open(path, 'rb+')
  2362.     finally:
  2363.         os.close(fd)
  2364.  
  2365.  
  2366.  
  2367. def _create_temporary(path):
  2368.     '''Create a temp file based on path and open for reading and writing.'''
  2369.     return _create_carefully('%s.%s.%s.%s' % (path, int(time.time()), socket.gethostname(), os.getpid()))
  2370.  
  2371.  
  2372. def _sync_flush(f):
  2373.     '''Ensure changes to file f are physically on disk.'''
  2374.     f.flush()
  2375.     if hasattr(os, 'fsync'):
  2376.         os.fsync(f.fileno())
  2377.     
  2378.  
  2379.  
  2380. def _sync_close(f):
  2381.     '''Close file f, ensuring all changes are physically on disk.'''
  2382.     _sync_flush(f)
  2383.     f.close()
  2384.  
  2385.  
  2386. class _Mailbox:
  2387.     
  2388.     def __init__(self, fp, factory = rfc822.Message):
  2389.         self.fp = fp
  2390.         self.seekp = 0
  2391.         self.factory = factory
  2392.  
  2393.     
  2394.     def __iter__(self):
  2395.         return iter(self.next, None)
  2396.  
  2397.     
  2398.     def next(self):
  2399.         while None:
  2400.             
  2401.             try:
  2402.                 self._search_start()
  2403.             except EOFError:
  2404.                 self.seekp = self.fp.tell()
  2405.                 return None
  2406.  
  2407.             start = self.fp.tell()
  2408.             self._search_end()
  2409.             self.seekp = stop = self.fp.tell()
  2410.             if start != stop:
  2411.                 break
  2412.                 continue
  2413.             continue
  2414.             return self.factory(_PartialFile(self.fp, start, stop))
  2415.  
  2416.  
  2417.  
  2418. class UnixMailbox(_Mailbox):
  2419.     
  2420.     def _search_start(self):
  2421.         while None:
  2422.             pos = self.fp.tell()
  2423.             line = self.fp.readline()
  2424.             if not line:
  2425.                 raise EOFError
  2426.             
  2427.             if line[:5] == 'From ' and self._isrealfromline(line):
  2428.                 self.fp.seek(pos)
  2429.                 return None
  2430.                 continue
  2431.             continue
  2432.             return None
  2433.  
  2434.     
  2435.     def _search_end(self):
  2436.         self.fp.readline()
  2437.         while None:
  2438.             pos = self.fp.tell()
  2439.             line = self.fp.readline()
  2440.             if not line:
  2441.                 return None
  2442.             
  2443.             if line[:5] == 'From ' and self._isrealfromline(line):
  2444.                 self.fp.seek(pos)
  2445.                 return None
  2446.                 continue
  2447.             continue
  2448.             return None
  2449.  
  2450.     _fromlinepattern = 'From \\s*[^\\s]+\\s+\\w\\w\\w\\s+\\w\\w\\w\\s+\\d?\\d\\s+\\d?\\d:\\d\\d(:\\d\\d)?(\\s+[^\\s]+)?\\s+\\d\\d\\d\\d\\s*[^\\s]*\\s*$'
  2451.     _regexp = None
  2452.     
  2453.     def _strict_isrealfromline(self, line):
  2454.         if not self._regexp:
  2455.             import re as re
  2456.             self._regexp = re.compile(self._fromlinepattern)
  2457.         
  2458.         return self._regexp.match(line)
  2459.  
  2460.     
  2461.     def _portable_isrealfromline(self, line):
  2462.         return True
  2463.  
  2464.     _isrealfromline = _strict_isrealfromline
  2465.  
  2466.  
  2467. class PortableUnixMailbox(UnixMailbox):
  2468.     _isrealfromline = UnixMailbox._portable_isrealfromline
  2469.  
  2470.  
  2471. class MmdfMailbox(_Mailbox):
  2472.     
  2473.     def _search_start(self):
  2474.         while None:
  2475.             line = self.fp.readline()
  2476.             if not line:
  2477.                 raise EOFError
  2478.             
  2479.             if line[:5] == '\x01\x01\x01\x01\n':
  2480.                 return None
  2481.                 continue
  2482.             continue
  2483.             return None
  2484.  
  2485.     
  2486.     def _search_end(self):
  2487.         while None:
  2488.             pos = self.fp.tell()
  2489.             line = self.fp.readline()
  2490.             if not line:
  2491.                 return None
  2492.             
  2493.             if line == '\x01\x01\x01\x01\n':
  2494.                 self.fp.seek(pos)
  2495.                 return None
  2496.                 continue
  2497.             continue
  2498.             return None
  2499.  
  2500.  
  2501.  
  2502. class MHMailbox:
  2503.     
  2504.     def __init__(self, dirname, factory = rfc822.Message):
  2505.         import re
  2506.         pat = re.compile('^[1-9][0-9]*$')
  2507.         self.dirname = dirname
  2508.         list = os.listdir(self.dirname)
  2509.         list = filter(pat.match, list)
  2510.         list = map(long, list)
  2511.         list.sort()
  2512.         self.boxes = map(str, list)
  2513.         self.boxes.reverse()
  2514.         self.factory = factory
  2515.  
  2516.     
  2517.     def __iter__(self):
  2518.         return iter(self.next, None)
  2519.  
  2520.     
  2521.     def next(self):
  2522.         if not self.boxes:
  2523.             return None
  2524.         
  2525.         fn = self.boxes.pop()
  2526.         fp = open(os.path.join(self.dirname, fn))
  2527.         msg = self.factory(fp)
  2528.         
  2529.         try:
  2530.             msg._mh_msgno = fn
  2531.         except (AttributeError, TypeError):
  2532.             pass
  2533.  
  2534.         return msg
  2535.  
  2536.  
  2537.  
  2538. class BabylMailbox(_Mailbox):
  2539.     
  2540.     def _search_start(self):
  2541.         while None:
  2542.             line = self.fp.readline()
  2543.             if not line:
  2544.                 raise EOFError
  2545.             
  2546.             if line == '*** EOOH ***\n':
  2547.                 return None
  2548.                 continue
  2549.             continue
  2550.             return None
  2551.  
  2552.     
  2553.     def _search_end(self):
  2554.         while None:
  2555.             pos = self.fp.tell()
  2556.             line = self.fp.readline()
  2557.             if not line:
  2558.                 return None
  2559.             
  2560.             if line == '\x1f\x0c\n' or line == '\x1f':
  2561.                 self.fp.seek(pos)
  2562.                 return None
  2563.                 continue
  2564.             continue
  2565.             return None
  2566.  
  2567.  
  2568.  
  2569. class Error(Exception):
  2570.     '''Raised for module-specific errors.'''
  2571.     pass
  2572.  
  2573.  
  2574. class NoSuchMailboxError(Error):
  2575.     """The specified mailbox does not exist and won't be created."""
  2576.     pass
  2577.  
  2578.  
  2579. class NotEmptyError(Error):
  2580.     '''The specified mailbox is not empty and deletion was requested.'''
  2581.     pass
  2582.  
  2583.  
  2584. class ExternalClashError(Error):
  2585.     '''Another process caused an action to fail.'''
  2586.     pass
  2587.  
  2588.  
  2589. class FormatError(Error):
  2590.     '''A file appears to have an invalid format.'''
  2591.     pass
  2592.  
  2593.